package org.maxgroundstation.simulation.service;

import java.awt.Color;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.maxgroundstation.simulation.MaxRobot;
import org.maxgroundstation.simulation.MaxRobotUtil;
import org.maxgroundstation.simulation.service.alive.ImAliveService;
import org.maxgroundstation.simulation.service.echo.EchoService;
import org.maxgroundstation.simulation.service.geometry.GeometryService;
import org.maxgroundstation.simulation.service.gps.GpsService;
import org.maxgroundstation.simulation.service.list.ListService;
import org.maxgroundstation.simulation.service.log.LogService;
import org.maxgroundstation.simulation.service.video.VideoService;
import org.maxgroundstation.simulation.service.wifi.WiFiService;

/**
 * Handles all service communication.
 */
public class ServiceCommunication {
	
	// Communication types.
	public static final int COMM_TCP = 0;
	public static final int COMM_UDP = 1;
	
	// Data members.
	private ServiceCommunicationUDP commUDP;
	private ServiceCommunicationTCP commTCP;
	private MaxRobot robot;
	private List<Service> services;
	private ClientManager clientManager;
	
	/**
	 * Constructor.
	 * @param robot the simulated robot.
	 */
	public ServiceCommunication(MaxRobot robot) {
		this.robot = robot;
		commUDP = new ServiceCommunicationUDP(this);
		commTCP = new ServiceCommunicationTCP(this);
		clientManager = new ClientManager();
		
		// Setup services.
		createServices();
		
		// Connect UDP & TCP services.
		try {
			commUDP.connect();
			commTCP.connect();
		}
		catch(IOException e) {
			log(e);
		}
	}
	
	public void destroy() {
		shutDownServices();
		
		try {
			disconnect();
		}
		catch (IOException e) {
			log(e);
		}
	}
	
	public ClientManager getClientManager() {
		return clientManager;
	}
	
    /**
     * Gets the current IP address.
     * @return IP address as string.
     */
    public String getIPAddress() {
    	String result = "";
    	try {
    		result = MaxRobotUtil.getLocalAddress().getHostAddress();
    	}
    	catch (IOException e) {
    		log(e);
    	}
    	return result;
    }
    
    /**
     * Gets the MAX robot.
     * @return robot.
     */
    public MaxRobot getRobot() {
    	return robot;
    }
    
    /**
     * Connect the service communication.
     * @throws IOException if error connecting the service communication.
     */
    public void connect() throws IOException {
    	
    	// Make communication connections.
    	commUDP.connect();
    	commTCP.connect();
    }
    
    /**
     * Disconnect the service communication.
     * @throws IOException if error disconnecting service communication.
     */
    public void disconnect() throws IOException {
    	
    	// Disconnect communication.
    	commUDP.disconnect();
    	commTCP.disconnect();
    	
    	// Clear all clients.
    	clientManager.clearClients();
    }
    
    /**
     * Handle a data response.
     * @param dataFrame the response data frame.
     * @param commType the communication type (UDP or TCP)
     * @throws Exception if error handling response data.
     */
    void handleResponseData(ServiceDataFrame dataFrame, int commType) throws Exception {
		
		String serviceID = dataFrame.getServiceID();
		
		log("Service handling response requested: " + serviceID.trim());
		
		// Determine service to handle response.
		Service handlingService = null;
		Iterator<Service> i = services.iterator();
		while (i.hasNext()) {
			Service service = i.next();
			if (service.getID().trim().equals(serviceID.trim())) handlingService = service;
		}
		
		if (handlingService != null) {
			log("Service " + serviceID.trim() + " found to handle response.");
			handlingService.handleResponse(dataFrame, commType);
		}
		else {
			log("Service " + serviceID.trim() + " not found to handle response.");
			throw new Exception("No service found to handle response for ID: " + serviceID);
		}
	}
    
    /**
     * Creates all the services available from the robot.
     */
    private void createServices() {
    	services = new ArrayList<Service>();
    	
    	services.add(new ImAliveService(this));
    	services.add(new EchoService(this));
    	services.add(new ListService(this));
    	services.add(new LogService(this));
    	
    	services.add(new VideoService(this, ServiceUtil.getServiceID("lvideo"), 
    	        "Left Video", Color.WHITE, Color.BLACK));
    	services.add(new VideoService(this, ServiceUtil.getServiceID("rvideo"), 
                "Right Video", Color.BLUE, Color.WHITE));
    	
    	services.add(new GeometryService(this));
    	services.add(new WiFiService(this));
    	services.add(new GpsService(this));
    }
    
    /**
     * Gets a list of all available services.
     * @return list of services.
     */
    public List<Service> getServices() {
    	return services;
    }
    
    /**
     * Checks if a service that has the given service ID exists.
     * @param serviceID the service ID.
     * @return true if service exists.
     */
    public boolean hasService(String serviceID) {
    	boolean result = false;
    	
    	Iterator<Service> i = services.iterator();
    	while (i.hasNext()) {
    		Service service = i.next();
    		if (service.getID().trim().equals(serviceID.trim())) result = true;
    	}
    	
    	return result;
    }
    
    /**
     * Gets a service the matches the given service ID.
     * @param serviceID the service ID.
     * @return service or null if none found.
     */
    public Service getService(String serviceID) {
    	Service result = null;
    	
    	Iterator<Service> i = services.iterator();
    	while (i.hasNext()) {
    		Service service = i.next();
    		if (service.getID().trim().equals(serviceID.trim())) result = service;
    	}
    	
    	return result;
    }
    
    /**
     * Shuts down all services.
     */
    private void shutDownServices() {
    	Iterator<Service> i = services.iterator();
    	while (i.hasNext()) {
    		i.next().shutDown();
    	}
    }
    
    /**
     * Sends a service post.
     * @param commType the communication type.
     * @param serviceID the service ID.
     * @param clientID the client ID.
     * @param data the data to send.
     * @throws Exception if error sending the post.
     */
    public void sendServicePost(int commType, String serviceID, String clientID, byte[] data) 
    		throws Exception {
    	if (commType == COMM_TCP) {
    		commTCP.sendServicePost(serviceID, clientID, data);
    	}
    	else if (commType == COMM_UDP) {
    		commUDP.sendServicePost(serviceID, clientID, data);
    	}
    	else throw new Exception("comm type: " + commType + " is unknown.");
    }
    
    /**
     * Logs a message.
     * @param message the log message.
     */
    public void log(String message) {
    	System.out.println(message);
    	
    	LogService logService = (LogService) getService(LogService.SERVICE_ID);
    	if (logService != null) logService.log(message);
    }
    
    /**
     * Logs an exception.
     * @param e the exception.
     */
    public void log(Exception e) {
    	e.printStackTrace(System.err);
    	
    	LogService logService = (LogService) getService(LogService.SERVICE_ID);
    	if (logService != null) logService.log(e);
    }
}